#include "graphics.h"
#include "templatevector.h"

// TODO global variable!
Uint32 g_DIRTY_RECTANGLE_FILL_COLOR = 0;

/**
 * @param a_filename
 * @return a pointer to an SDL_Surface optimized to the current SDL display format
 */
SDL_Surface * VSDL_loadImage(const char * a_filename)
{
	SDL_Surface* loadedImage = NULL;//Temporary storage for the image that's loaded
	SDL_Surface* optimizedImage = NULL;//The optimized image that will be used
	loadedImage = SDL_LoadBMP( a_filename ); //Load the image
	if( loadedImage != NULL )//If nothing went wrong in loading the image
	{ 
		optimizedImage = SDL_DisplayFormat( loadedImage ); //Create an optimized image
		SDL_FreeSurface( loadedImage );//Free the old image
	}
	return optimizedImage;//Return the optimized image
}

/**
 * @param a_surface an SDL_Surface to set a transparent color for
 * @param a_transparentRGB example: 0xff00ff for magenta, 0x0000ff for red, (R | G << 8 | B << 16)
 */
void VSDL_setTransparentColor(SDL_Surface * a_surface, const int & a_transparentRGB)
{
	Uint32 colorkey = SDL_MapRGB( a_surface->format, 	//Map the color key
		0xff & (a_transparentRGB), 
		0xff & (a_transparentRGB >> 8), 
		0xff & (a_transparentRGB >> 16));
	SDL_SetColorKey( a_surface, SDL_SRCCOLORKEY, colorkey );
}

/**
 * @param a_source the SDL surface (tile bitmap) to treat as the tile source
 * @param a_tileSize an SDL_Rect who's width and height values are the width
 * and height of the tiles in the tile source
 * @param a_index the tile from the tile source to draw, with the upper-left
 * most tile being index 0.
 * @param a_dest what surface to draw the tile to
 * @param a_x
 * @param a_y coordinate to draw the tile to on the destination surface
 */
void VSDL_drawTile(SDL_Surface * a_source, SDL_Rect * a_tile, const int & a_index, 
				   SDL_Surface * a_dest, int a_x, int a_y)
{
	int dist = a_index * a_tile->w;
	SDL_Rect src = {dist % a_source->w, (dist / a_source->w) * a_tile->h, a_tile->w, a_tile->h}, 
			dest = {a_x, a_y, 0,0};
	while(src.y >= a_source->h)
	{
		src.y -= a_source->h;
	}
	SDL_BlitSurface( a_source, &src, a_dest, &dest); 
}

// TODO global variable!
/** PixelFont to use when none is specified */
PixelFont g_defaultFont;

/** TODO document me with an explanaition */
PixelFont * VSDL_getDefaultFont(){return &g_defaultFont;}

/**
 * @param a_defaultFontImage source of the pixel font. Note: you must release this yourself
 * @param a_tileSize size of each mono-space character, laid out in a grid
 * @param a_startingLetter what is the first printable letter (0th letter in the source image)
 * @param a_numLetters how many printable letters this pixel font has
 */
void VSDL_setDefaultMonospaceFont(SDL_Surface * a_defaultFontImage, const SPoint & a_defaultFontCharSize,
							 const int & a_firstLetter, const int & a_numLetters)
{
	g_defaultFont.setMonoSpaceAscii(a_defaultFontImage, a_defaultFontCharSize, a_firstLetter, a_numLetters);
}

/**
 * @param a_defaultFontImage source of the pixel font (pixel markers to show glyph size).
 * Note: you must release this yourself SDL_Surface
 * @param a_startingLetter what is the first printable letter (0th letter in the source image)
 * @param a_numLetters how many printable letters this pixel font has
 */
void VSDL_setDefaultVariableWidthFont(SDL_Surface * a_defaultFontImage,
							 const int & a_firstLetter, const int & a_numLetters)
{
	g_defaultFont.setVariableWidthAscii(a_defaultFontImage, a_firstLetter, a_numLetters);
}

/** TODO document me with an explanaition */
void VSDL_drawString(PixelFont * a_font, char * a_text, SDL_Surface * a_dest, const SPoint & a_point)
{
	SRect tile;
	SPoint p(a_point);
	for(int i = 0; a_text[i]; ++i)
	{
		tile = a_font->getImageSourceOf(a_text[i]);
		VSDL_drawSurfaceToSurface(a_dest, p, a_font->getImageSource(), &tile, 0, 1); 
		//VSDL_drawTile(a_asciiSource, a_tileSize, a_text[i], a_dest, x, a_y);
		p.addX(tile.getWidth());
	}
}

/**
 * @param a_text using the defualt pixel font, write this text...
 * @param a_dest on this surface...
 * @param a_point at this location (upper left coordinate of 0th letter)
 */
void VSDL_drawString(char * a_text, SDL_Surface * a_dest, const SPoint & a_point)
{
	VSDL_drawString(VSDL_getDefaultFont(), a_text, a_dest, a_point);
}

/**
 * @param a_sidesToDraw (DRAW_RECT_MINY_SIDE | DRAW_RECT_MAXY_SIDE | DRAW_RECT_MINX_SIDE | DRAW_RECT_MAXX_SIDE)
 */
void VSDL_drawRect(SDL_Surface * a_surface, SDL_Rect * a_rect, Uint32 a_rgbColor, 
				   const int & a_borderSize, const int & a_sidesToDraw)
{
	Uint32 formattedColor = SDL_MapRGB( a_surface->format,
		0xff & (a_rgbColor >> 16), 
		0xff & (a_rgbColor >> 8), 
		0xff & (a_rgbColor));
	SDL_Rect border = *a_rect;
	if(a_sidesToDraw & DRAW_RECT_MINY_SIDE)
	{
		border.h = a_borderSize;
		VSDL_FillRect(a_surface, &border, formattedColor);	// top
		border = *a_rect;
	}
	if(a_sidesToDraw & DRAW_RECT_MAXY_SIDE)
	{
		border.h = a_borderSize;
		border.y = a_rect->y + a_rect->h - a_borderSize;
		VSDL_FillRect(a_surface, &border, formattedColor);	// bottom
	}
	if(a_rect->h > a_borderSize*2)
	{
		if(a_sidesToDraw & DRAW_RECT_MINX_SIDE)
		{
			border.y = a_rect->y + a_borderSize;
			border.h = a_rect->h - a_borderSize*2;
			border.w = a_borderSize;
			border.x = a_rect->x;
			VSDL_FillRect(a_surface, &border, formattedColor);	// left
		}
		if(a_sidesToDraw & DRAW_RECT_MAXX_SIDE)
		{
			border.y = a_rect->y + a_borderSize;
			border.h = a_rect->h - a_borderSize*2;
			border.w = a_borderSize;
			border.x = a_rect->x + a_rect->w - a_borderSize;
			VSDL_FillRect(a_surface, &border, formattedColor);	// right
		}
	}
}

void VSDL_drawRect(SDL_Surface * a_surface, SDL_Rect * a_rect, Uint32 a_rgbColor, const int & a_borderSize)
{
	VSDL_drawRect(a_surface, a_rect, a_rgbColor, a_borderSize, DRAW_RECT_ALL_SIDES);
}
/**
 * @param a_surface where to draw the broken rectangle
 * @param a_rect what rectangle to draw broken
 * @param a_rgbColor what color to draw the rectangle in
 * @param a_borderSize how many pixels thick to make the lines
 * @param a_break how large a break to make between broken bits
 * TODO make the dotted lines look nicer (more consistent, a "crawling ants" visual would be nice)
 */
void VSDL_drawRectBroken(SDL_Surface * a_surface, SDL_Rect * a_rect, const RGB_QUAD & a_rgbColor, 
					const int & a_borderSize, const int & a_break)
{
	Uint32 formattedColor = RGBQUAD_toSurfaceColor(a_rgbColor, a_surface->format);
	SRect border(*a_rect);
	border.h = a_borderSize;
	SRect chunk(border);
	// a copy of the advancing chunk of the broken rect, needed because FillRect modifies arguments...
	SRect cursor;
	SPoint m(a_break*2, 0);
	for(chunk.setWidth(a_break); chunk.getMaxX() < border.getMaxX(); chunk.move(m))
	{
		cursor.set(chunk);
		VSDL_FillRect(a_surface, &cursor, formattedColor);	// top
	}

	chunk.y = a_rect->y + a_rect->h - a_borderSize;
	for(chunk.setX(a_rect->x); chunk.getMaxX() < border.getMaxX(); chunk.move(m))
	{
		cursor.set(chunk);
		VSDL_FillRect(a_surface, &cursor, formattedColor);	// bottom
	}

	if(a_rect->h > a_borderSize*2)
	{
		border.y = a_rect->y + a_borderSize;
		border.h = a_rect->h - a_borderSize*2;
		border.w = a_borderSize;
		chunk.set(border);
		m.set(0, a_break*2);
		for(chunk.setHeight(a_break); chunk.getMaxY() < border.getMaxY(); chunk.move(m))
		{
			cursor.set(chunk);
			VSDL_FillRect(a_surface, &cursor, formattedColor);	// left
		}

		border.x = a_rect->x + a_rect->w - a_borderSize;
		chunk.set(border);
		for(chunk.setHeight(a_break); chunk.getMaxY() < border.getMaxY(); chunk.move(m))
		{
			cursor.set(chunk);
			VSDL_FillRect(a_surface, &cursor, formattedColor);	// right
		}
	}
}

/**
 * @param a_surface an SDL_Surface
 * @param a where on the SDL surface to change the pixel
 * @param pixel the pixel color or index, already formated to work with this given SDL_Surface
 */
void VSDL_setSurfacePixel(SDL_Surface* a_surface, const SPoint & a, const Uint32 & pixel) {
	if(a.x < 0 || a.x >= a_surface->w || a.y < 0 || a.y >= a_surface->h)
		return;
	int bpp = a_surface->format->BytesPerPixel;
	/* p is the address to the pixel we want to set */
	Uint8 *p = (Uint8 *)a_surface->pixels + a.y * a_surface->pitch + a.x * bpp;
	switch(bpp) {
	case 1:		*p = pixel;					break;
	case 2:		*(Uint16 *)p = pixel;		break;
	case 3:
		if(SDL_BYTEORDER == SDL_BIG_ENDIAN) {
			p[0] = (pixel >> 16) & 0xff;
			p[1] = (pixel >> 8) & 0xff;
			p[2] = pixel & 0xff;
		} else {
			p[0] = pixel & 0xff;
			p[1] = (pixel >> 8) & 0xff;
			p[2] = (pixel >> 16) & 0xff;
		}
		break;
	case 4:		*(Uint32 *)p = pixel;		break;
	}
}

/**
 * @param a_surface an SDL_Surface
 * @param a where on the SDL surface to get the pixel
 * @return the pixel color or index formated to work with this given SDL_Surface
 */
Uint32 VSDL_getSurfacePixel(const SDL_Surface * a_surface, const SPoint & a) {
	int bpp = a_surface->format->BytesPerPixel;
	/* p is the address to the pixel we want to retrieve */
	Uint8 *p = (Uint8 *)a_surface->pixels + a.y * a_surface->pitch + a.x * bpp;
	switch(bpp) {
	case 1:		return *p;
	case 2:		return *(Uint16 *)p;
	case 3:
		if(SDL_BYTEORDER == SDL_BIG_ENDIAN) {
			return p[0] << 16 | p[1] << 8 | p[2];
		} else {
			return p[0] | p[1] << 8 | p[2] << 16;
		}
		break;
	case 4:		return *(Uint32 *)p;
	default:	return 0;
	}
}

// TODO implement this pattern to make VSDL_getSurfacePixel more efficient
//Uint8 getPixel8(const int & x, const int & y) const {
//	Uint8* pixels = (Uint8*)m_surface->pixels;
//	return pixels[y * m_surface->w + x];
//}
//Uint16 getPixel16(const int & x, const int & y) const {
//	Uint16* pixels = (Uint16*)m_surface->pixels;
//	return pixels[y * m_surface->w + x];
//}
//Uint32 Sprite::getPixel32(const int & x, const int & y) const {
//	Uint32* pixels = (Uint32*)m_surface->pixels;
//	return pixels[y * m_surface->w + x];
//}

/**
 * @param a_src an image to flip
 * @param a_flip how to clip the image
 */
SDL_Surface * VSDL_createFlippedSurface(SDL_Surface * a_src, const int & flip)
{
	int h = ((flip&VSDL_FLIP_XY)?(a_src->w):(a_src->h)), 
		w = ((flip&VSDL_FLIP_XY)?(a_src->h):(a_src->w)), x, y;
	SDL_PixelFormat * f = a_src->format;
	SDL_Surface * dest = SDL_CreateRGBSurface(SDL_SWSURFACE, w, h, 
		f->BitsPerPixel, f->Rmask, f->Gmask, f->Bmask, 
		((a_src->flags & SDL_SRCCOLORKEY)?f->Amask:0));
	// prevent the image from being modified while it is being copied
	if(SDL_MUSTLOCK(a_src)){
		SDL_LockSurface(a_src);
	}
	for(int r = 0; r < h; ++r)
	{
		for(int c = 0; c < w; ++c)
		{
			x = ((flip&VSDL_FLIP_X)?((w-1)-c):c);
			y = ((flip&VSDL_FLIP_Y)?((h-1)-r):r);
			VSDL_setSurfacePixel(dest, 
				SPoint(r,c), 
				VSDL_getSurfacePixel(a_src, 
					((flip&VSDL_FLIP_XY)?SPoint(x,y):SPoint(y,x))));
		}
	}
	if(SDL_MUSTLOCK(a_src)){
		SDL_UnlockSurface(a_src);
	}
	return dest;
}

void VSDL_drawSurfaceToSurface(SDL_Surface * a_dest, const SPoint & a_destLoc, 
	SDL_Surface * a_src, SDL_Rect * a_srcRect, const int & flip, const int & a_pixelSize)
{
	int h = ((flip&VSDL_FLIP_XY)?(a_srcRect->w):(a_srcRect->h)), 
		w = ((flip&VSDL_FLIP_XY)?(a_srcRect->h):(a_srcRect->w)), srcx, srcy;
	SDL_PixelFormat * f = a_src->format;
	// prevent the image from being modified while it is being copied
	if(SDL_MUSTLOCK(a_src)){
		SDL_LockSurface(a_src);
	}
	bool hasTransparency = (a_src->flags & SDL_SRCCOLORKEY) != 0;
	Uint32 pixel, transparent = a_src->format->colorkey;
	SRect pixelArea;
	for(int r = 0; r < h; ++r)
	{
		for(int c = 0; c < w; ++c)
		{
			srcx = ((flip&VSDL_FLIP_X)?((w-1)-c):c)+a_srcRect->x;
			srcy = ((flip&VSDL_FLIP_Y)?((h-1)-r):r)+a_srcRect->y;
			pixel = VSDL_getSurfacePixel(a_src, SPoint(srcx,srcy));
			// draw non-transparent pixels
			if(!hasTransparency || pixel != transparent)
			{
				SPoint pixelLoc = ((flip&VSDL_FLIP_XY)
								?SPoint((r)*a_pixelSize+a_destLoc.x,
										(c)*a_pixelSize+a_destLoc.y)
								:SPoint((c)*a_pixelSize+a_destLoc.x,
										(r)*a_pixelSize+a_destLoc.y));
				pixelArea.set(pixelLoc.x,pixelLoc.y,a_pixelSize,a_pixelSize);
				if(a_pixelSize == 1)
				{
					// single pixel
					VSDL_setSurfacePixel(a_dest, pixelLoc, pixel);
				}
				else
				{
					// pixels as filled rectangles
					VSDL_FillRect(a_dest, &pixelArea, pixel);
				}
				//// pixels as empty boxes
				//for(int py = 0; py < a_pixelSize; ++py)
				//{
				//	VSDL_setSurfacePixel(a_dest, SPoint(pixelLoc.x, pixelLoc.y+py), pixel);
				//	if(a_pixelSize>1)VSDL_setSurfacePixel(a_dest, SPoint(pixelLoc.x+a_pixelSize-1, pixelLoc.y+py), pixel);
				//}
				//if(a_pixelSize>2)for(int px = 1; px < a_pixelSize-1; ++px)
				//{
				//	VSDL_setSurfacePixel(a_dest, SPoint(pixelLoc.x+px, pixelLoc.y), pixel);
				//	VSDL_setSurfacePixel(a_dest, SPoint(pixelLoc.x+px, pixelLoc.y+a_pixelSize-1), pixel);
				//}
			}
		}
	}
	if(SDL_MUSTLOCK(a_src)){
		SDL_UnlockSurface(a_src);
	}
}

/**
 * @param a_dest where to draw the source image to
 * @param a_destLoc where in the destination image to draw the source image
 * @param bmp the BitMaP image to draw to the destination image
 * @param a_srcRect which part of the source image to draw
 * @param a_flip an SDL_FLIP_* variable to modify how the destination image is drawn
 * @param a_pixelSize how many pixels squared source image should be in destination image
 */
void VSDL_drawBitmapToSurface(SDL_Surface * a_dest, const SPoint & a_destLoc, 
				BitMaP * bmp, const SRect & a_srcRect, const int & flip, 
				const int & a_pixelSize, //const RGB_QUAD & a_transparentColor)
				const int & a_transparentColorIndex)
{
	int h = ((flip&VSDL_FLIP_XY)?(a_srcRect.getWidth()):(a_srcRect.getHeight())), 
		w = ((flip&VSDL_FLIP_XY)?(a_srcRect.getHeight()):(a_srcRect.getWidth()));
	SDL_PixelFormat * f = a_dest->format;
	Uint32 pixel
		//, transparent = bmp->getColorIndex(a_transparentColor)
		;
	bool hasTransparency = a_transparentColorIndex >= 0;
	SRect clip;
	SPoint bmpsize(bmp->getDimension());
	SDL_GetClipRect(a_dest, (SDL_Rect*)&clip);
	SPoint srcPixel;
	SRect pixelArea;
	for(int r = 0; r < h; ++r)
	{
		for(int c = 0; c < w; ++c)
		{
			srcPixel.set(
				((flip&VSDL_FLIP_X)?((w-1)-c):c)+a_srcRect.getX(),
				((flip&VSDL_FLIP_Y)?((h-1)-r):r)+a_srcRect.getY());
			pixel = bmp->getPixelAt(srcPixel);
			// draw non-transparent pixels
			if(!hasTransparency || pixel != a_transparentColorIndex)//transparent)
			{
				SPoint pixelLoc = ((flip&VSDL_FLIP_XY)
								?SPoint((r)*a_pixelSize+a_destLoc.x,
										(c)*a_pixelSize+a_destLoc.y)
								:SPoint((c)*a_pixelSize+a_destLoc.x,
										(r)*a_pixelSize+a_destLoc.y));
				pixelArea.set(pixelLoc.x,pixelLoc.y,a_pixelSize,a_pixelSize);
				Uint32 pixelColor = RGBQUAD_toSurfaceColor(*bmp->getColor(pixel), f);
				if(a_pixelSize == 1 && clip.contains(pixelLoc))
				{
					// single pixel
					VSDL_setSurfacePixel(a_dest, pixelLoc, pixelColor);
				}
				else
				{
					// pixels as filled rectangles
					VSDL_FillRect(a_dest, &pixelArea, pixelColor);
				}
				//// pixels as empty boxes
				//for(int py = 0; py < a_pixelSize; ++py)
				//{
				//	VSDL_setSurfacePixel(a_dest, SPoint(pixelLoc.x, pixelLoc.y+py), pixel);
				//	if(a_pixelSize>1)VSDL_setSurfacePixel(a_dest, SPoint(pixelLoc.x+a_pixelSize-1, pixelLoc.y+py), pixel);
				//}
				//if(a_pixelSize>2)for(int px = 1; px < a_pixelSize-1; ++px)
				//{
				//	VSDL_setSurfacePixel(a_dest, SPoint(pixelLoc.x+px, pixelLoc.y), pixel);
				//	VSDL_setSurfacePixel(a_dest, SPoint(pixelLoc.x+px, pixelLoc.y+a_pixelSize-1), pixel);
				//}
			}
		}
	}
}

Uint32 RGBQUAD_toSurfaceColor(const RGB_QUAD & rgb, SDL_PixelFormat * format)
{
	return SDL_MapRGB(format, rgb.R, rgb.G, rgb.B);
}

/**
 * convert a BitMaP to an SDL_Surface
 * @param a_bmp a BitMaP image to convert to an SDL_Surface
 * @param f the SDL_PixelFormat to use, probably screen->format.
 */
SDL_Surface * VSDL_bitmapToSurface(const BitMaP * a_bmp, SDL_PixelFormat * f)
{
	SDL_Surface * surface = SDL_CreateRGBSurface(
		SDL_SWSURFACE, a_bmp->getWidth(), a_bmp->getHeight(), 
		f->BitsPerPixel, f->Rmask, f->Gmask, f->Bmask, 0);
	SPoint p, max(a_bmp->getWidth(), a_bmp->getHeight());
	int i = 0;
	RGB_QUAD * color;
	do
	{
		color = a_bmp->getPixelColorAt(p);
		VSDL_setSurfacePixel(surface, p, SDL_MapRGB(f, color->R, color->G, color->B));
		i = 0;
	}
	while(p.iterate(i, SPoint::ZERO, max));
	return surface;
}

TemplateVector<SRect> g_clipHistory;
/**
 * @param screen to shrink the clip rectangle of
 * @param rectangle that is the narrower clip
 * @return true if the new clip is still valid. if the clip was invalid, it was not pushed.
 */
bool VSDL_pushClip(SDL_Surface * a_surface, const SRect & a_clipRect)
{
	SRect oldClip, newClip(a_clipRect);
	SDL_GetClipRect(a_surface, &oldClip);
	newClip.clip(oldClip);
	if(newClip.isValid())
	{
		g_clipHistory.add(oldClip);
		SDL_SetClipRect(a_surface, &newClip);
		return true;
	}
	return false;
}
/** goes back to the previous clip */
void VSDL_popClip(SDL_Surface * a_surface)
{
	if(g_clipHistory.size() > 0)
	{
		SDL_SetClipRect(a_surface, &g_clipHistory.pop());
	}
}

SRect VSDL_getClip(SDL_Surface * a_surface)
{
	SRect clip;
	SDL_GetClipRect(a_surface, &clip);
	return clip;
}
